﻿using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Text.Json;
using System.Text.Unicode;

namespace Quickuse.Logging.EsLogger
{
    /// <summary>
    /// Es Logger
    /// </summary>
    internal sealed class EsLogger : Logger
    {
        private StringBuilder _logBuilder = new StringBuilder();
        private StringBuilder _sbmessage = new StringBuilder();
        private IEsLoggerSettings _esLoggerSettings;
        public bool _includeScope;
        private List<LogLevel> _logLevels;

        /// <summary>
        /// EsLogger
        /// </summary>
        /// <param name="categoryName">category Name</param>
        /// <param name="esLoggerSettings"></param>
        /// <param name="logCallback"></param>
        /// <param name="filter"></param>
        /// <param name="scopeProvider"></param>
        public EsLogger(string categoryName, IEsLoggerSettings esLoggerSettings, Action<string, LogLevel> logCallback, Func<string, LogLevel, bool> filter, IExternalScopeProvider scopeProvider)
            : base(categoryName, logCallback, filter)
        {
            ScopeProvider = scopeProvider;
            _includeScope = ScopeProvider != null;
            _logLevels = esLoggerSettings.Levels;
            _esLoggerSettings = esLoggerSettings;
        }

        /// <summary>
        /// Log
        /// </summary>
        /// <typeparam name="TState">TState</typeparam>
        /// <param name="logLevel">logLevel</param>
        /// <param name="eventId">eventId</param>
        /// <param name="state">state</param>
        /// <param name="exception">exception</param>
        /// <param name="formatter">formatter</param>
        public override void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            if (!IsEnabled(logLevel))
            {
                return;
            }

            if (_logLevels.Count > 0 && !_logLevels.Contains(logLevel))
            {
                return;
            }

            if (formatter == null)
            {
                throw new ArgumentNullException(nameof(formatter));
            }

            var message = formatter?.Invoke(state, exception);

            if (!string.IsNullOrEmpty(message) || exception != null)
            {
                WriteMessage(logLevel, CategoryName, eventId.Id, message, exception, state);
            }
        }

        /// <summary>
        /// WriteMessage
        /// </summary>
        /// <typeparam name="TState">TState</typeparam>
        /// <param name="logLevel">logLevel</param>
        /// <param name="logName">logName</param>
        /// <param name="eventId">eventId</param>
        /// <param name="message">message</param>
        /// <param name="exception">exception</param>
        /// <param name="state">state</param>
        public void WriteMessage<TState>(LogLevel logLevel, string logName, int eventId, string message, Exception exception, TState state)
        {
            var messagePadding = string.Empty;
            var logLevelString = logLevel.GetLogLevelString();

            // 根据配置是否输出 Scope 信息
            if (_includeScope)
            {
                // Example:
                // INFO: ConsoleApp.Program[10]
                //       Request received

                // category and event id
                _sbmessage.AppendFormat($"{logName}[{eventId}]");
                _sbmessage.AppendLine();
                // scope information
                messagePadding = new string(' ', logLevelString.Length + 2);
            }

            if (!string.IsNullOrEmpty(message))
            {
                if (_includeScope)
                {
                    message = $"{messagePadding}{message}".Replace(Environment.NewLine, $"{Environment.NewLine}{messagePadding}");
                }
                else
                {
                    message = $"{message}";
                }
                _sbmessage.AppendLine(message);
            }

            _logBuilder.Append(new
            {
                appid = _esLoggerSettings.AppId,
                clusterseq = _esLoggerSettings.ClusterSeq,
                timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
                loglevel = logLevel.ToString(),
                logname = logName,
                eventid = eventId,
                message = _sbmessage.ToString(),
                exception = exception.FormatException(state as NameValueCollection)
            }.ToJson());

            if (_logBuilder.Length > 0)
            {
                LogCallback?.Invoke(_logBuilder.ToString(), logLevel);
            }

            _logBuilder.Clear();
            _sbmessage.Clear();

            if (_logBuilder.Capacity > 1024)
            {
                _logBuilder.Capacity = 1024;
            }

            if (_sbmessage.Capacity > 16)
            {
                _sbmessage.Capacity = 16;
            }
        }
    }
}
